#pragma once
#include "index.hpp"
#include "log.hpp"
#include <jsoncpp/json/json.h>
#include <algorithm>
#include <unordered_map>
namespace ns_searcher
{
    struct InvertedElemsum
    {
        uint64_t doc_id;
        int weight;
        std::vector<std::string> keys;
        InvertedElemsum()
            : weight(0), doc_id(0)
        {
        }
    };

    class searcher
    {
    public:
        searcher()
        {
        }
        ~searcher()
        {
        }
        void InitSearcher(const std::string &input)
        {
            // 获取index索引单例
            _index = ns_index::index::Getinstance();
            if (_index == nullptr)
            {
                std::cout << "get index err!" << std::endl;
                return;
            }
            LOG(NORMAL, "获取单例成功...");
            // 建立索引
            _index->BuildIndex(input);
            LOG(NORMAL, "建立索引成功...");
        }
        // query: 搜索关键字
        // json_string: 返回给用户浏览器的搜索结果
        void Search(const std::string &query, std::string *json_string)
        {
            // 对输入的关键词进行分词
            std::vector<std::string> words;
            ns_util::JiebaUtil::CutString(query, &words);

            // 对每个关键词进行查找
            // 建立index是忽略大小写，所以搜索，关键字也需要
            // 通过index查找倒排索引，返回的是倒排拉链
            std::unordered_map<std::uint64_t, InvertedElemsum> insmap;
            std::vector<InvertedElemsum> inverted_all;
            // 可能存在重复获取的情况，比如不同的关键字对应的文档id相同
            // 最后插入到inverted_all中就会有重复，所以使用一个map进行去重
            for (auto word : words)
            {
                // word需要全部转化为小写
                boost::to_lower(word);
                ns_index::InvertedList *inverlist = _index->GetInvertedList(word);
                if (inverlist == nullptr)
                {
                    continue;
                }

                // inverted_all.insert(inverted_all.end(),inverlist->begin(),inverlist->end());
                for (const auto &elem : *inverlist)
                {
                    auto &iter = insmap[elem.doc_id];
                    iter.doc_id = elem.doc_id;
                    iter.weight += elem.weight;
                    iter.keys.push_back(elem.key);
                }
            }

            for (const auto &it : insmap)
            {
                inverted_all.push_back(it.second);
            }
            // 合并排序:汇总结果，按照相关性->权重排序
            // 按照权重进行排序
            std::sort(inverted_all.begin(), inverted_all.end(), [](const InvertedElemsum &e1, const InvertedElemsum &e2)
                      { return e1.weight > e2.weight; });

            // 构建返回结果，使用json
            Json::Value root;

            for (auto &item : inverted_all)
            {
                // 通过id得到网页内容
                ns_index::Datainfo_t *doc = _index->GetForwardIndex(item.doc_id);
                if (doc == nullptr)
                {
                    continue;
                }

                Json::Value elem;
                elem["title"] = doc->title;
                elem["desc"] = GetDec(doc, item.keys[0]);
                elem["url"] = doc->url;
                elem["id"] = (int)doc->doc_id;
                elem["weight"] = item.weight;
                root.append(elem);
            }

            // 将结果写入到传入的字符串中
            Json::StyledWriter writer;
            *json_string = writer.write(root);
        }

    private:
        // 获取简介
        std::string GetDec(ns_index::Datainfo_t *doc, const std::string &key)
        {
            // 找到word在doc_content中首次出现，然后往前找50字节，往后找100字节
            // 截取出这部分内容
            const int prev_step = 50;
            const int next_step = 100;
            // 找到首次出现的位置
            // 这里需要着重注意：
            // 由于doc0>content保留的是html中的内容，是有大写也有小写的。
            // 所以需要将html中的内容全部转化为小写
            // 这里使用c++11里面的searcher函数
            auto iter = std::search(doc->content.begin(), doc->content.end(), key.begin(), key.end(), [](int x, int y)
                                    { return std::tolower(x) == std::tolower(y); });
            if (iter == doc->content.end())
            {
                return "None1";
            }

            int pos = std::distance(doc->content.begin(), iter); // 得到两个迭代器的距离
            // 判断当前位置的前面和后面是否分别有多于50和多于100的字符
            int start = 0, end = doc->content.size() - 1;
            if (pos > start + prev_step)
            {
                start = pos - prev_step;
            }
            if (end - next_step > pos)
            {
                end = pos + next_step;
            }
            // 判断是否出现start>end的情况
            if (start >= end)
            {
                return "None2";
            }
            // 切割字符串
            std::string res = doc->content.substr(start, end - start);
            res += "...";
            return res;
        }

        ns_index::index *_index;
    };
};